test: close the false-green mechanisms, wire the dead CI gates (+ coverage gap analysis) - #436
Conversation
Audits what the suite covers per layer and ranks the gaps. Measured: lib/ is at 91.6% line coverage (716/782), so the risk is not in the Ruby. It is in the layers the suite treats as rendered output, plus two mechanisms that report green without testing anything: - test/integration/hugo_pipeline_test.rb skips the whole asset-pipeline suite when the Hugo build fails, which is the failure it guards. - 42 conditional assertion guards (`if el ... assert ... end`) pass when the element is absent. Also found: rake test:html_proofer is invoked by no workflow, hook, or script; rake test:integration never gates a PR; the contact and free-consultation form field names come from site params that Hugo renders as "" when missing, with no test reading the DOM; FAQ schema ships on 10 service pages untested while its four sibling schemas each have a test file. OKF: .okf/build/ci-gates.md claimed a toolchain drift test "fails the build when any copy diverges" - that test was deleted on 2026-08-01, so the pins are synced by convention with zero enforcement. Corrected, and the same stale phrase dropped from the Rakefile :guards comment. Analysis and proposals only; no test changes in this commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fza9okyuQ5QdXesVQ85n3b
|
Warning Review limit reached
Next review available in: 26 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (24)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Acts on items 1-4 of the gap analysis in the previous commit. rake test:unit goes 272 -> 285 runs, 5723 -> 5935 assertions, 0 failures. Integration suite fails loudly. hugo_pipeline_test replaces its two *_ready? predicates with build_failure, flunked in setup: it separates "hugo not on PATH" from a real build error and prints the last 30 lines of build output. Verified by shimming hugo to /bin/false - the suite fails instead of skipping 11 tests into a green report. The two "No * CSS file found" skips became refute_nil. Two dead CI gates wired. rake test:integration now runs on PRs as its own Asset Pipeline job, separate from unit_tests because it drives two Hugo builds of its own. rake test:html_proofer - previously invoked by no workflow, hook, or script - joins test:links in one rake invocation; build_for_linkcheck is memoized per process so the pair shares a single production build, since two steps would build the site twice and blow the job timeout that already forced setup-hugo build: 'false'. Two schema test files were dead code. breadcrumb_schema_test and service_schema_test were commented out in full behind stale "restore when <X> schema implemented in reverted HTML" TODOs; the build emits both today. Uncommented, 3 tests each. A test file existing is not coverage. New tests for two untested paths. lead_forms_test asserts a non-empty absolute action and a non-empty name on every field of the contact, free-consultation, and career forms - Hugo renders a missing [params.forms.*] key as "" without failing the build, and the screenshot baselines cannot see it. RED-verified by renaming first_name in hugo.toml: the failure names the field id and the config key. faq_schema_test covers the FAQPage block on the service pages, including a sweep asserting every page declaring faqs emits it. Guard sweep, baseof and 404. Presence assertions where the element exists; dead branches removed where it does not - .logo-image-main is gone from the codebase, there is no meta[name=referrer], and the site has no search form. The mermaid SRI test asserted the retired jsdelivr+SRI implementation while running against index.html, which never loads mermaid; retargeted to a diagram page and rewritten to assert same-origin. Two 404 tests only ever called puts "INFO: ...", and one evaluated external_scripts.length and discarded it - those now assert invariants that hold (the 404 page pulls zero third-party scripts or stylesheets). ~41 guard candidates remain in 5 files, enumerated in the doc. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fza9okyuQ5QdXesVQ85n3b
rake test:unit: 285 -> 276 runs, 5935 -> 5967 assertions, 0 failures. Fewer tests, more assertions - 9 removed tests could not fail, and the survivors gained real presence checks. list_template_test found a live bug the guard was hiding. test_list_page_date_information matched items with "article, .post, .post-item, .entry" - a list omitting `.blog-post`, which is the ONLY class the blog index renders. It selected zero items on every run and `if items.any?` made that read as a pass. Both tests now share ITEM_SELECTOR so the two cannot drift again. Promoted to real assertions where the build has the element: post items (a blog index that lists nothing is this page's headline failure), pagination, per-item dates, heading hierarchy, homepage Organization schema, homepage social links, preload tags. Removed where the site has no such feature, so no assertion ever ran: list-page filtering/sorting, RSS head link, search, breadcrumb elements (the JSON-LD BreadcrumbList it does emit is covered by breadcrumb_schema_test), author and category/tag elements, homepage breadcrumbs, homepage CSP meta, homepage analytics (environment-gated out of the test build). Each removal is recorded in place with what to assert if the feature ships. Replaced discarded-value lines with the invariant behind them: the blog index and homepage both load zero third-party scripts and stylesheets, which is why they need no dns-prefetch. The old code computed `external_scripts.length + external_stylesheets.length` and threw it away. setup no longer skips. list_template skipped all 13 tests when no list page existed; the blog index vanishing is the regression, not a reason to report green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fza9okyuQ5QdXesVQ85n3b
Records the live bug the guard was hiding (list_template's item selector omitted .blog-post, the only class the blog index renders, so it matched zero items on every run), the classification rule that made the sweep tractable, and the accurate remaining count: 32 of 61 candidates addressed, ~29 left in 3 files plus singletons. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fza9okyuQ5QdXesVQ85n3b
rake test:unit: 275 runs / 6086 assertions / 0 failures, from the 272 / 5723 baseline. Assertions up 363 while the test count dropped by 13. Four bugs the guards were hiding. A guard does not just miss future regressions, it hides present ones: 1. single_template_test.rb never tested a single page. @test_pages led with "blog/index.html" and .first picked it, so 376 lines nominally covering single.html ran against the LIST page. Pinned to a real post via SINGLE_PAGE - which immediately exposed bug 4. 2. Two item selectors omitted .blog-post, the only class the blog index renders, so test_list_page_date_information and test_blog_post_partials matched zero items on every run. 3. asset_url_validation used `css_urls.any? do |url| assert ... end`. any? short-circuits on the first truthy block result and assert returns true, so only the FIRST stylesheet was ever checked. 4. An over-strict a11y rule flagged image-only links as having no accessible name. A link wrapping an image takes its name from the image alt (WCAG 2.1 SC 1.1.1); blog posts wrap YouTube thumbnails this way. Fixed the rule, not the markup. Skip-style guards are the same defect one level up and are gone too: list_template skipped 13 tests with no list page, 404_template skipped 12 with no 404.html, template_cleanup_validation carried 9 `next unless test_page_exists?` plus 3 `return unless` (now one assert_empty in setup, helper deleted), and seo_schema called skip "Schema N is empty - might indicate template issue" on exactly the template issue it exists to catch. Retargeted rather than deleted where a partial renders elsewhere: hugo_partials' social-sharing test checked index and about-us, neither of which renders blog/share.html, so it now uses a post. Method: check the built page first, then decide. Element present -> presence assertion. Element absent -> dead branch, delete it and record in place what to assert if the feature ships. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fza9okyuQ5QdXesVQ85n3b
…e-analysis-5zm7nj # Conflicts: # .okf/log.md
Three runs of the new gates gave real numbers. Broken Internal Links took 3.5 min warm and 10.7 min cold, right after master moved and invalidated resources/_gen - at its original 10-minute cap that run would have gone red for nothing but cache state (the raise to 15 in the previous commit already covers it). Asset Pipeline needs the same treatment. The suite itself is ~10 min: two full Hugo builds, and the dev-environment one cannot reuse the production-keyed resource cache, so it reprocesses images. On top of that, actions/checkout was observed taking 7 minutes on a slow runner - 17 minutes of wall clock against a 15-minute cap. Raised to 25. Sizing a timeout from the average rather than the worst observed run buys a gate that flakes, and a gate that flakes teaches reviewers to ignore red. The runner minutes are not worth that trade. The per-step timings are the diagnosis tell here: at job level the 7-minute checkout is invisible and reads as a slow test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fza9okyuQ5QdXesVQ85n3b
What
An audit of what the test suite actually covers, plus the fixes for every gap it found in the "coverage the suite claims but does not have" category.
rake test:unit: 272 → 275 runs, 5723 → 6096 assertions, 0 failures. Assertions up 373 while the test count went down by 13. That ratio is the point — the suite got smaller and started checking more.Doc:
docs/20-29-testing-qa/20.10-test-coverage-gap-analysis-reference.md.The audit
lib/is healthy at 91.6% line coverage (716/782 relevant lines, SimpleCov over the pure-Ruby tests). The risk was never in the Ruby. It was in the layers the suite treats as rendered output, and in gates that reported green without testing anything.Fixed
1. The integration suite skipped itself when the build failed
hugo_pipeline_test.rbcalledskipwhen the Hugo build failed — the asset pipeline is exactly what a broken build takes down, so the one regression these tests exist to catch reported 0 failures with all 11 tests skipped.Now
build_failure+flunk, separating "hugo not on PATH" from a real build error and printing the last 30 lines of build output. Verified by shimminghugoto/bin/false.2. Two CI gates existed but ran nowhere
rake test:html_prooferwas invoked by no workflow, hook, or script. It now joinstest:linksinlink-check.yml— as one rake invocation, because both default to the sameOUTPUT_DIRand each triggers its own production build.build_for_linkcheckis memoized per process so the pair shares one build; two steps would build the site twice, the exact double-build that forcedsetup-hugo build: 'false'on this job.rake test:integrationnever gated a PR. Now its ownAsset Pipelinejob.3. Two schema test files were 100% commented out
breadcrumb_schema_test.rbandservice_schema_test.rbhad zero active tests behind staleTODO: Restore when <X> schema implemented in reverted HTMLmarkers. The build emits both today. Uncommented, 3 tests each.A test file existing is not coverage — grep for
def test_, not for the filename.4. The lead-gen forms had no behavioral test
contact-form.htmlrenders every field'snamefrom[params.forms.contact]. Hugo renders a missing param as""with no error, the page stays pixel-identical to the screenshot baseline, and leads submit blank.New
test/unit/lead_forms_test.rb. RED verified, not assumed — renamingfirst_nameinhugo.toml:5. FAQ structured data had no test
New
faq_schema_test.rb(6 tests), including a sweep asserting every service page declaringfaqsemits the schema — so a template guard that stops matching turns red instead of silently dropping rich results across the section.6. Conditional-assertion guards — all 61 triaged
if el ... assert ... endpasses when the element is absent. Every candidate was checked against the built page: element present → presence assertion; element absent → dead branch, deleted with an in-place note on what to assert if the feature ships.This turned up four live bugs the guards were hiding:
single_template_test.rbnever tested a single page.@test_pagesled with"blog/index.html"and.firstpicked it, so 376 lines nominally coveringsingle.htmlran against the list page. Retargeting it to a real post immediately exposed bug 4..blog-post— the only class the blog index renders.test_list_page_date_informationandtest_blog_post_partialsmatched zero items on every run since they were written.css_urls.any? do |url| assert ... end—any?short-circuits on the first truthy block result andassertreturns true, so only the first stylesheet was ever checked.alt(WCAG 2.1 SC 1.1.1). Fixed the rule, not the markup.Skip-style guards were the same defect one level up and are gone too:
list_templateskipped 13 tests with no list page;404_templateskipped 12 with no404.html;template_cleanup_validationhad 9next unless test_page_exists?+ 3return unless;seo_schemacalledskip "Schema N is empty - might indicate template issue"on exactly the template issue it exists to catch.7. CI timeouts sized from the measured worst case
Running the new gates produced real numbers.
Broken Internal Links: 3.5 min warm, 10.7 min cold (right after master moved and invalidatedresources/_gen) — at its original 10-min cap that run would have gone red for nothing but cache state.Asset Pipeline: ~10 min for the suite, plus anactions/checkoutmeasured at 7 min on one slow runner = 17 min against a 15-min cap.Raised to 15 and 25. A gate that flakes on timeout teaches reviewers to ignore red, which costs more than the runner minutes.
Also
.okf/build/ci-gates.mdclaimed a toolchain drift test "fails the build when any copy diverges" — that test was deleted 2026-08-01, so the pins are convention-only with zero enforcement. Corrected, plus the same stale phrase in theRakefile:guardscomment.Still open (documented, not in this PR)
Validator ratchet for blog/marketing content (course 0/82 em-dash files vs blog 208/607 — the rule holds exactly where a validator enforces it); DOM tests for the 4 page templates no test parses;
static/sw.js;lib/sync/sources/sanity.rb(129 lines, referenced by nothing) delete-or-test;bin/qtestpages_formapping.Testing
rake test:unit— 275 runs, 6096 assertions, 0 failuresrake test:integration— 11 runs, 0 failures, and confirmed to fail loudly on a broken buildrake test:guards— 5 runs, 0 failuresUnit Tests,build,Broken Internal Links,Asset PipelineVisual suites deliberately not run. The diff touches no
themes/,layouts/,assets/,*.css, or content bodies — onlytest/,Rakefile,.github/workflows/,docs/, and.okf/. Per CLAUDE.md thebin/test+bin/dtestpair gates changes to visual surfaces; there are none here.One environment note:
parse_html_fileuses bareFile.read, so under a US-ASCII default encoding (noLANG) Nokogiri aborts and 73 template tests fail for a non-template reason. Run underLANG=C.UTF-8.🤖 Generated with Claude Code
https://claude.ai/code/session_01Fza9okyuQ5QdXesVQ85n3b